<?php
namespace mylib;

/**
 * Smtp 邮件发送类
 *
 * @$host_name  is used in HELO command
 * @relay_host 邮箱服务器
 * @$smtp_port 邮件服务器端口
 * @$user 邮箱账户
 * @pass email密码
 * auth 这里面的一个true是表示使用身份验证,否则不使用身份验证.
 * @return $sent
 */

class Smtp
{
    protected $host_name;
    protected $relay_host;
    protected $smtp_port;
    protected $time_out;
    protected $user;
    protected $pass;
    protected $auth;
    protected $log_file;
    protected $debug;
    protected $sock;

    /**
     * __construct() 初始化邮件发送服务
     * 默认配置为 huwhwewis@163.com 这个邮箱发送
     *
     * @param string $relay_host
     * @param integer $smtp_port
     * @param boolean $auth
     * @param string $user
     * @param string $pass
     */
    public function __construct($relay_host = 'smtp.163.com', $smtp_port = 25, $auth = true, $user = 'huwhereis@163.com', $pass = 'humail001')
    {
        $this->host_name = "localhost";
        
        $this->relay_host = $relay_host;
        
        $this->smtp_port = $smtp_port;
        
        $this->time_out = 30;
        
        $this->user = $user;
        
        $this->pass = $pass;
        
        $this->auth = true;
        
        $this->log_file = '';
        
        $this->debug = false;
        
        $this->sock = false;
    }

    /**
     * Main function sendMail
     *
     * @param string $to
     * @param string $from
     * @param string $subject
     * @param string $body
     * @param string $mailtype //信件类型，文本:text；网页：HTML
     * @param string $cc
     * @param string $bcc
     * @param string $additional_headers
     * @return boolean $tent
     */
    public function sendMail($to, $from, $subject = "", $body = "", $mailtype = "", $cc = "", $bcc = "", $additional_headers = "")
    {
        $mail_from = $this->getAddress($this->stripComment($from));

        $body = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $body);

        $header="";

        $header.= "MIME-Version:1.0\r\n";

        if ($mailtype == "HTML") {
            $header .= "Content-Type:text/html\r\n";
        }

        $header .= "To: " . $to . "\r\n";

        if ($cc != "") {
            $header .= "Cc: " . $cc . "\r\n";
        }

        $header .= "From: $from<" . $from . ">\r\n";

        $header .= "Subject: " . $subject . "\r\n";

        $header .= $additional_headers;

        $header .= "Date: " . date("r") . "\r\n";

        $header .= "X-Mailer:By Redhat (PHP/" . phpversion() . ")\r\n";

        list ($msec, $sec) = explode(" ", microtime());

        $header .= "Message-ID: <" . date("YmdHis", $sec) . "." . ($msec * 1000000) . "." . $mail_from . ">\r\n";

        $TO = explode(",", $this->stripComment($to));

        if ($cc != "") {
            $TO = array_merge($TO, explode(",", $this->stripComment($cc)));
        }

        if ($bcc != "") {
            $TO = array_merge($TO, explode(",", $this->stripComment($bcc)));
        }

        $sent = true;

        foreach ($TO as $rcpt_to) {
            $rcpt_to = $this->getAddress($rcpt_to);

            if (!$this->smtpSockopen($rcpt_to)) {
                $this->logWrite("Error: Cannot send email to " . $rcpt_to . "\n");

                $sent = false;

                continue;
            }

            if ($this->smtpSend($this->host_name, $mail_from, $rcpt_to, $header, $body)) {
                $this->logWrite("E-mail has been sent to <" . $rcpt_to . ">\n");
            } else {
                $this->logWrite("Error: Cannot send email to <" . $rcpt_to . ">\n");

                $sent = false;
            }

            fclose($this->sock);

            $this->logWrite("Disconnected from remote host\n");
        }

        return $sent;
    }

    /* Private public functions */

    protected function smtpSend($helo, $from, $to, $header, $body = "")
    {
        if (!$this->smtpPutcmd("HELO", $helo)) {
            return $this->smtpError("sending HELO command");
        }
        // auth
        if ($this->auth) {
            if (!$this->smtpPutcmd("AUTH LOGIN", base64_encode($this->user))) {
                return $this->smtpError("sending HELO command");
            }

            if (!$this->smtpPutcmd("", base64_encode($this->pass))) {
                return $this->smtpError("sending HELO command");
            }
        }

        if (!$this->smtpPutcmd("MAIL", "FROM:<" . $from . ">")) {
            return $this->smtpError("sending MAIL FROM command");
        }

        if (!$this->smtpPutcmd("RCPT", "TO:<" . $to . ">")) {
            return $this->smtpError("sending RCPT TO command");
        }

        if (!$this->smtpPutcmd("DATA")) {
            return $this->smtpError("sending DATA command");
        }

        if (!$this->smtpMessage($header, $body)) {
            return $this->smtpError("sending message");
        }

        if (!$this->smtpEom()) {
            return $this->smtpError("sending <CR><LF>.<CR><LF> [EOM]");
        }

        if (!$this->smtpPutcmd("QUIT")) {
            return $this->smtpError("sending QUIT command");
        }

        return true;
    }

    protected function smtpSockopen($address)
    {
        if ($this->relay_host == "") {
            return $this->smtpSockopenMx($address);
        } else {
            return $this->smtpSockopenRelay();
        }
    }

    protected function smtpSockopenRelay()
    {
        $this->logWrite("Trying to " . $this->relay_host . ":" . $this->smtp_port . "\n");

        $this->sock = @ fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out);

        if (!($this->sock && $this->smtpOk())) {
            $this->logWrite("Error: Cannot connenct to relay host " . $this->relay_host . "\n");

            $this->logWrite("Error: " . $errstr . " (" . $errno . ")\n");

            return false;
        }

        $this->logWrite("Connected to relay host " . $this->relay_host . "\n");

        return true;
        ;
    }

    protected function smtpSockopenMx($address)
    {
        $domain = preg_replace("/^.+@([^@]+)$/", "\1", $address);

        if (!@ getmxrr($domain, $MXHOSTS)) {
            $this->logWrite("Error: Cannot resolve MX \"" . $domain . "\"\n");

            return false;
        }

        foreach ($MXHOSTS as $host) {
            $this->logWrite("Trying to " . $host . ":" . $this->smtp_port . "\n");

            $this->sock = @ fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out);

            if (!($this->sock && $this->smtpOk())) {
                $this->logWrite("Warning: Cannot connect to mx host " . $host . "\n");

                $this->logWrite("Error: " . $errstr . " (" . $errno . ")\n");

                continue;
            }

            $this->logWrite("Connected to mx host " . $host . "\n");

            return true;
        }

        $this->logWrite("Error: Cannot connect to any mx hosts (" . implode(", ", $MXHOSTS) . ")\n");

        return false;
    }

    protected function smtpMessage($header, $body)
    {
        fputs($this->sock, $header . "\r\n" . $body);

        $this->smtpDebug("> " . str_replace("\r\n", "\n" . "> ", $header . "\n> " . $body . "\n> "));

        return true;
    }

    protected function smtpEom()
    {
        fputs($this->sock, "\r\n.\r\n");

        $this->smtpDebug(". [EOM]\n");

        return $this->smtpOk();
    }

    protected function smtpOk()
    {
        $response = str_replace("\r\n", "", fgets($this->sock, 512));

        $this->smtpDebug($response . "\n");

        if (!preg_match("/^[23]/", $response)) {
            fputs($this->sock, "QUIT\r\n");

            fgets($this->sock, 512);

            $this->logWrite("Error: Remote host returned \"" . $response . "\"\n");

            return false;
        }

        return true;
    }

    protected function smtpPutcmd($cmd, $arg = "")
    {
        if ($arg != "") {
            if ($cmd == "") {
                $cmd = $arg;
            } else {
                $cmd = $cmd . " " . $arg;
            }
        }

        fputs($this->sock, $cmd . "\r\n");

        $this->smtpDebug("> " . $cmd . "\n");

        return $this->smtpOk();
    }

    protected function smtpError($string)
    {
        $this->logWrite("Error: Error occurred while " . $string . ".\n");

        return false;
    }

    protected function logWrite($message)
    {
        $this->smtpDebug($message);

        if ($this->log_file == "") {
            return true;
        }

        $message = date("M d H:i:s ") . get_current_user() . "[" . getmypid() . "]: " . $message;

        if (!@ file_exists($this->log_file) || !($fp = @ fopen($this->log_file, "a"))) {
            $this->smtpDebug("Warning: Cannot open log file \"" . $this->log_file . "\"\n");

            return false;
            ;
        }

        flock($fp, LOCK_EX);

        fputs($fp, $message);

        fclose($fp);

        return true;
    }

    protected function stripComment($address)
    {
        $comment = "/\([^()]*\)/";

        while (preg_match($comment, $address)) {
            $address = preg_replace($comment, "", $address);
        }

        return $address;
    }

    protected function getAddress($address)
    {
        $address = preg_replace("/([ \t\r\n])+/", "", $address);

        $address = preg_replace("/^.*<(.+)>.*$/", "\1", $address);

        return $address;
    }

    protected function smtpDebug($message)
    {
        if ($this->debug) {
            echo $message . "
			;";
        }
    }
}
